﻿using S7CommPlusDriver;
using S7CommPlusDriver.ClientApi;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace S7CommPlusDriverCore.Extension
{
    /// <summary>
    /// 状态机管理类
    /// </summary>
    public class StateMachineManager
    {
        private TaskCompletionSource<bool> _whenStart;

        private CancellationTokenSource _stateMachineCts;

        private int heartBeatDefNumber;

        private SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);

        private readonly string ipAddress;

        /// <summary>
        /// 状态机状态
        /// </summary>
        public StateMachineStatus StateMachineStatus { get; set; }

        /// <summary>
        /// 标签管理器
        /// </summary>
        public TagsManager TagsManager { get; set; }

        /// <summary>
        /// 心跳周期
        /// </summary>
        public int KeepAlive { get; }

        /// <summary>
        /// PLC连接对象
        /// </summary>
        public S7CommPlusConnection S7CommPlusConnection { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="s7CommPlusConnection"></param>
        /// <param name="keepAlive"></param>
        public StateMachineManager(S7CommPlusConnection s7CommPlusConnection, string ipAddress, int keepAlive, CancellationTokenSource cancellationTokenSource)
        {
            _whenStart = new TaskCompletionSource<bool>();
            S7CommPlusConnection = s7CommPlusConnection;
            this.ipAddress = ipAddress;
            TagsManager = new TagsManager();
            KeepAlive = keepAlive;

            _stateMachineCts = cancellationTokenSource;
            Task.Run(() => StateMachineAsync(_stateMachineCts.Token));
        }

        /// <summary>
        /// 初始化特性标注的标签
        /// </summary>
        /// <param name="tagAware"></param>
        public void AddInstance(ITagAware tagBase)
        {
            TagsManager.InitInstance(tagBase);
        }

        /// <summary>
        /// 状态机管理
        /// </summary>
        private async Task StateMachineAsync(CancellationToken token)
        {
            while (!token.IsCancellationRequested)
            {
                //等待连接
                await _whenStart.Task.ConfigureAwait(false);

                if (TagsManager.ReadPlcTags.Any())
                {
                    if (S7CommPlusConnection.ReadValues(TagsManager.ReadPlcTags) != 0)
                    {
                        heartBeatDefNumber += 1;
                        Thread.Sleep(KeepAlive);
                    }
                    else
                    {
                        heartBeatDefNumber = 0;
                    }
                }

                _semaphoreSlim.Wait();
                if (TagsManager.WritePlcTags.Any())
                {
                    S7CommPlusConnection.WriteValues(TagsManager.WritePlcTags);
                    TagsManager.WritePlcTags.Clear();
                }
                _semaphoreSlim.Release();

                //心跳连续10次失败，连接失败
                if (heartBeatDefNumber >= 10)
                {
                    Stop();
                    Start();
                    heartBeatDefNumber = 0;
                }
                Thread.Sleep(3);
            }
        }

        public void AddWriteValue(PlcTag plcTag)
        {
            _semaphoreSlim.Wait();

            TagsManager.WritePlcTags.Add(plcTag);

            _semaphoreSlim.Release();
        }

        public void Start()
        {
            StateMachineStatus = StateMachineStatus.Connecting;

            if (S7CommPlusConnection.Connect(ipAddress) == 0)
            {
                S7CommPlusConnection.BrowseAllTags(out var varInfoList);

                varInfoList.ForEach((varInfo) =>
                {
                    TagsManager.AddTag(Helper.CreatePlcTag(varInfo.TagName, new ItemAddress(varInfo.TagId), varInfo.Datatype));
                });

                StateMachineStatus = StateMachineStatus.Connected;


                _whenStart.TrySetResult(true);

            }
            else
            {
                StateMachineStatus = StateMachineStatus.Error;
            }
        }

        public void Stop()
        {
            S7CommPlusConnection.Disconnect();

            StateMachineStatus = StateMachineStatus.Disonnected;

            _whenStart = new TaskCompletionSource<bool>();
        }
    }

    public enum StateMachineStatus
    {
        Disonnected,

        Connecting,

        Connected,

        Error,
    }
}
